// 模拟App连接和通讯
package main

import (
	"acs/client"
	"acs/comet/config"
	"acs/comet/proto"
	"acs/pbmodel"
	"acs/taskprocess"
	"errors"
	"fmt"

	log "github.com/cihub/seelog"
	pb "github.com/golang/protobuf/proto"
	flags "github.com/jessevdk/go-flags"
	"math/rand"
	"os"
	"io"
	"strconv"
)

func main() {
	var args struct {
		Concurrency uint16 `short:"n" default:"3" required:"yes" description:"模拟客户端数量。"`
		ServerAddr string `short:"s"  required:"yes" description:"服务端地址, 如:127.0.0.1:8082"`
		TestUIDs []int64 `short:"u"  description:"测试的用户id,可设置多个, 如: -u 102 -u 332 -u 89"`
		UidFile string `short:"f"  description:"包含uid的文件,uid由逗号分隔，如: -f /tmp/test_uid.txt。当指定uid文件后，忽略-u参数"`
	}
	argParser := flags.NewNamedParser("app mock", flags.Default|flags.IgnoreUnknown)
	argParser.AddGroup("Mock parameters", "", &args)

	_, err := argParser.Parse()

	if args.UidFile == "" && len(args.TestUIDs) < 1 {
		println("必须指定测试用户ID '-u' 或者测试用户uid文件 '-f'")
		argParser.WriteHelp(os.Stdout)
		os.Exit(1)
	}
	config.InitConfig()
	if err != nil {
		return
	}
	logger, err := log.LoggerFromConfigAsString(`<seelog>
    <outputs formatid="common">
        <console />
    </outputs>
    <formats>
    <format id="common" format="[%Level]%Date(2006/01/02 15:04:05.999)[%File:%Line] %Msg%n"/>
</formats>
</seelog>
	`)
	if err != nil {
		panic(err)
	}
	err = log.ReplaceLogger(logger)

	hungCh := make(chan int, 1)

	concurrent := int(args.Concurrency)
	log.Infof("Start tests with %v mockup clients.", concurrent)
	var uidFile *os.File
	if args.UidFile != "" {
		uidFile, err = os.Open(args.UidFile)
		if err != nil {
			panic(log.Errorf("Failed to open uid file: %v", err))
		}
	}
	var lastUid int64
	var allUidsRead bool
	for counter := concurrent; counter > 0; counter-- {
		var uid int64
		if uidFile == nil {
			uid = args.TestUIDs[counter%len(args.TestUIDs)]
		} else if !allUidsRead{
			nbs := []byte{}
			// 读取一个uid, uid由数字组成，非数字分隔。
			for{
				nb := make([]byte, 1)
				_, err := uidFile.Read(nb)
				if err != nil {
					if err == io.EOF {
						allUidsRead = true
						if len(nbs) < 1 && lastUid < 1{
							panic(log.Criticalf("not enough uid to read from file. need %v, read %v uids.", concurrent, concurrent-counter))
						} else if len(nbs) < 1{
							uid = lastUid
						}
						break
					} else {
						panic(log.Criticalf("Failed to read more uid: %v.  need %v, read %v uids.", err, concurrent, concurrent-counter))
					}
				} else {
					if len(nbs) == 0 && (nbs[0] == 9 || nbs[0] == 32) {
						continue
					} else if nbs[0] != '*' {
						nbs = append(nbs, nb[0])
					} else {
						break
					}
				}
			}
			if uid < 1{
				uid, err = strconv.ParseInt(string(nbs), 10, 64 )
				if err != nil {
					panic(log.Criticalf("failed to parse uid: %v", err))
				}
				lastUid = uid
			}

		} else if allUidsRead {
			if lastUid < 1 {
				panic(log.Criticalf("Failed to read more uid: %v.  need %v, read %v uids.", err, concurrent, concurrent-counter))
			} else {
				uid = lastUid
			}
		}
		go startClient(uid, args.ServerAddr, logger)
	}
	if uidFile != nil {
		uidFile.Close()
	}
	<-hungCh
}

func startClient(uid int64, serverAddr string, logger log.LoggerInterface){
	log.Debugf("starting mock app with uid[%v]", uid)
	c, err := client.NewClient(client.ClientConfig{
		Addr:           serverAddr,
		HeartBeatTtl:   5000 + rand.Int63n(20000),
		Logger:         logger,
		DataEncryptIV:  config.Conf.DataEncryptIV,
		DataEncryptKey: config.Conf.DataEncryptKey,
		HandshakeMessage: client.HandshakeMessage{
			Cmd: proto.CmdRegister,
			ReqMsg: &pbmodel.RegisterInfo{
				Appver:   pb.String("3.0.1"),
				BundleID:  pb.String("com.jumei.live.android"),
				System:    pb.Int32(int32(taskprocess.PlatformAndroid)),
				OSVersion: pb.String("9.3.1"),
				Brand:     pb.String("Huawei"),
				Model:     pb.String("MateS"),
				UID:       pb.String(fmt.Sprintf("%v", uid)),
				UUID:      pb.String("X-xx"),
				LPID:      pb.String("patch-3.0.2-001"),
				LSID:      pb.String("3.0.1-211"),
			},
			Resp: &pbmodel.RegisterInfoResp{},
		},
	})
	if err != nil {
		log.Warnf("%v", err)
		return
	}
	patchCmdData := new(TestPatchMessage)
	patchCmdRespData := new(pbmodel.PatchfinResp)
	c.RegisterRequestHandler(patchHandler, proto.CmdPatch, patchCmdData, patchCmdRespData)
}

type TestPatchMessage struct {
	pbmodel.Patch
}

func (_ *TestPatchMessage) New() pb.Message {
	return new(pbmodel.Patch)
}
func patchHandler(patchInfoI pb.Message) (resp pb.Message, err error) {
	patchInfo, ok := patchInfoI.(*pbmodel.Patch)
	if !ok {
		err = errors.New("type error: patch message should be *pbmodel.Patch")
	}
	log.Debugf("Got ACS message: %+v", *patchInfo)
	patchResp := new(pbmodel.PatchResp)
	patchResp.Msg = pb.String("ok")
	resp = patchResp
	return
}
